package com.spilliwilli.android;

import java.util.Iterator;
import java.util.Random;

import org.anddev.andengine.engine.Engine;
import org.anddev.andengine.engine.camera.Camera;
import org.anddev.andengine.engine.camera.ZoomCamera;
import org.anddev.andengine.engine.handler.IUpdateHandler;
import org.anddev.andengine.engine.handler.physics.PhysicsHandler;
import org.anddev.andengine.engine.options.EngineOptions;
import org.anddev.andengine.engine.options.EngineOptions.ScreenOrientation;
import org.anddev.andengine.engine.options.resolutionpolicy.RatioResolutionPolicy;
import org.anddev.andengine.entity.primitive.Rectangle;
import org.anddev.andengine.entity.scene.Scene;
import org.anddev.andengine.entity.scene.Scene.IOnAreaTouchListener;
import org.anddev.andengine.entity.scene.Scene.IOnSceneTouchListener;
import org.anddev.andengine.entity.scene.Scene.ITouchArea;
import org.anddev.andengine.entity.scene.background.ColorBackground;
import org.anddev.andengine.entity.shape.IShape;
import org.anddev.andengine.entity.shape.Shape;
import org.anddev.andengine.entity.sprite.AnimatedSprite;
import org.anddev.andengine.entity.util.FPSLogger;
import org.anddev.andengine.extension.input.touch.controller.MultiTouch;
import org.anddev.andengine.extension.input.touch.controller.MultiTouchController;
import org.anddev.andengine.extension.input.touch.detector.PinchZoomDetector;
import org.anddev.andengine.extension.input.touch.detector.PinchZoomDetector.IPinchZoomDetectorListener;
import org.anddev.andengine.extension.input.touch.exception.MultiTouchException;
import org.anddev.andengine.extension.physics.box2d.FixedStepPhysicsWorld;
import org.anddev.andengine.extension.physics.box2d.PhysicsConnector;
import org.anddev.andengine.extension.physics.box2d.PhysicsFactory;
import org.anddev.andengine.extension.physics.box2d.PhysicsWorld;
import org.anddev.andengine.extension.physics.box2d.util.Vector2Pool;
import org.anddev.andengine.extension.physics.box2d.util.constants.PhysicsConstants;
import org.anddev.andengine.input.touch.TouchEvent;
import org.anddev.andengine.input.touch.detector.ScrollDetector;
import org.anddev.andengine.input.touch.detector.SurfaceScrollDetector;
import org.anddev.andengine.input.touch.detector.ScrollDetector.IScrollDetectorListener;
import org.anddev.andengine.opengl.texture.Texture;
import org.anddev.andengine.opengl.texture.TextureOptions;
import org.anddev.andengine.opengl.texture.region.TextureRegionFactory;
import org.anddev.andengine.opengl.texture.region.TiledTextureRegion;
import org.anddev.andengine.sensor.accelerometer.AccelerometerData;
import org.anddev.andengine.sensor.accelerometer.IAccelerometerListener;
import org.anddev.andengine.ui.activity.BaseGameActivity;
import org.json.JSONException;
import org.json.JSONObject;

import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.ContactImpulse;
import com.badlogic.gdx.physics.box2d.ContactListener;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.Manifold;
import com.badlogic.gdx.physics.box2d.joints.MouseJoint;
import com.badlogic.gdx.physics.box2d.joints.MouseJointDef;

public class MyGame extends BaseGameActivity implements IAccelerometerListener, IOnSceneTouchListener, IOnAreaTouchListener, ContactListener, IUpdateHandler, IScrollDetectorListener,
		IPinchZoomDetectorListener {
	// ===========================================================
	// Constants
	// ===========================================================

	private static final int CAMERA_WIDTH = 720;
	private static final int CAMERA_HEIGHT = 480;

	private ZoomCamera mZoomCamera;

	private static final float DEMO_VELOCITY = 100.0f;

	private static final FixtureDef FIXTURE_DEF = PhysicsFactory.createFixtureDef(1, 0.5f, 0.5f);

	// ===========================================================
	// Fields
	// ===========================================================

	private Texture mTexture;

	private TiledTextureRegion mBoxFaceTextureRegion;
	private TiledTextureRegion mCircleFaceTextureRegion;

	private SurfaceScrollDetector mScrollDetector;
	private PinchZoomDetector mPinchZoomDetector;
	private float mPinchZoomStartedCameraZoomFactor;

	private Scene mScene;

	private PhysicsWorld mPhysicsWorld;
	private int mFaceCount = 0;

	private MouseJoint mMouseJointActive;
	private Body mGroundBody;

	private TiledTextureRegion mFaceTextureRegion;

	// ===========================================================
	// Constructors
	// ===========================================================

	// ===========================================================
	// Getter & Setter
	// ===========================================================

	// ===========================================================
	// Methods for/from SuperClass/Interfaces
	// ===========================================================

	@Override
	public Engine onLoadEngine() {
		this.mZoomCamera = new ZoomCamera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT);
		final EngineOptions engineOptions = new EngineOptions(true, ScreenOrientation.LANDSCAPE, new RatioResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT), this.mZoomCamera);
		engineOptions.getTouchOptions().setRunOnUpdateThread(true);
		final Engine engine = new Engine(engineOptions);
		try {
			if (MultiTouch.isSupported(this)) {
				engine.setTouchController(new MultiTouchController());
			} else {
				Toast.makeText(this, "Sorry your device does NOT support MultiTouch!\n\n(No PinchZoom is possible!)", Toast.LENGTH_LONG).show();
			}
		} catch (final MultiTouchException e) {
			Toast.makeText(this, "Sorry your Android Version does NOT support MultiTouch!\n\n(No PinchZoom is possible!)", Toast.LENGTH_LONG).show();
		}

		return engine;
	}

	@Override
	public void onLoadResources() {
		/* Textures. */
		this.mTexture = new Texture(64, 64, TextureOptions.BILINEAR_PREMULTIPLYALPHA);
		TextureRegionFactory.setAssetBasePath("gfx/");

		this.mFaceTextureRegion = TextureRegionFactory.createTiledFromAsset(this.mTexture, this, "face_circle_tiled.png", 0, 0, 2, 1);

		/* TextureRegions. */
		// TextureRegionFactory.createTiledFromAsset(this.mTexture, this,
		// "gfx/face_circle_tiled.png", 0, 0, 2, 1);

		this.mBoxFaceTextureRegion = TextureRegionFactory.createTiledFromAsset(this.mTexture, this, "face_box_tiled.png", 0, 0, 2, 1); // 64x32
		this.mCircleFaceTextureRegion = TextureRegionFactory.createTiledFromAsset(this.mTexture, this, "face_circle_tiled.png", 0, 32, 2, 1); // 64x32
		this.mEngine.getTextureManager().loadTexture(this.mTexture);
	}

	@Override
	public Scene onLoadScene() {
		this.mEngine.registerUpdateHandler(new FPSLogger());

		this.mScene = new Scene();
		this.mScene.setBackground(new ColorBackground(0, 0, 0));
		this.mScene.setOnSceneTouchListener(this);
		this.mScene.setOnAreaTouchListener(this);
		this.mScene.registerUpdateHandler(this);

		// this.mPhysicsWorld = new PhysicsWorld(new Vector2(0,
		// SensorManager.GRAVITY_EARTH), false);
		this.mPhysicsWorld = new FixedStepPhysicsWorld(30, new Vector2(0, 0), false, 8, 1);
		mPhysicsWorld.setContactListener(this);
		this.mGroundBody = this.mPhysicsWorld.createBody(new BodyDef());

		final Shape ground = new Rectangle(0, CAMERA_HEIGHT - 2, CAMERA_WIDTH, 2);
		final Shape roof = new Rectangle(0, 0, CAMERA_WIDTH, 2);
		final Shape left = new Rectangle(0, 0, 2, CAMERA_HEIGHT);
		final Shape right = new Rectangle(CAMERA_WIDTH - 2, 0, 2, CAMERA_HEIGHT);

		final FixtureDef wallFixtureDef = PhysicsFactory.createFixtureDef(0, 0.5f, 0.5f);
		PhysicsFactory.createBoxBody(this.mPhysicsWorld, ground, BodyType.StaticBody, wallFixtureDef);
		PhysicsFactory.createBoxBody(this.mPhysicsWorld, roof, BodyType.StaticBody, wallFixtureDef);
		Body leftBody = PhysicsFactory.createBoxBody(this.mPhysicsWorld, left, BodyType.StaticBody, wallFixtureDef);
		PhysicsFactory.createBoxBody(this.mPhysicsWorld, right, BodyType.StaticBody, wallFixtureDef);

		// TODO

		leftBody.setUserData(makeUserData(101, leftBody, roof));
		this.mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(roof, leftBody, false, false));

		this.mScene.attachChild(ground);
		this.mScene.attachChild(roof);
		this.mScene.attachChild(left);
		this.mScene.attachChild(right);

		addBall();

		this.mScrollDetector = new SurfaceScrollDetector(this);
		if (MultiTouch.isSupportedByAndroidVersion()) {
			try {
				this.mPinchZoomDetector = new PinchZoomDetector(this);
			} catch (final MultiTouchException e) {
				this.mPinchZoomDetector = null;
			}
		} else {
			this.mPinchZoomDetector = null;
		}

		this.mScene.setTouchAreaBindingEnabled(true);
		this.mScene.registerUpdateHandler(this.mPhysicsWorld);

		return this.mScene;
	}

	private void addBall() {
		Random random = new Random();
		// int centerX = (CAMERA_WIDTH -
		// this.mCircleFaceTextureRegion.getWidth()) / 2;
		int centerY = (CAMERA_HEIGHT - this.mCircleFaceTextureRegion.getHeight()) / 2;
		int centerX = random.nextInt(this.mCircleFaceTextureRegion.getWidth());
		final Ball ball = new Ball(centerX, centerY, this.mCircleFaceTextureRegion);
		final PhysicsHandler physicsHandler = new PhysicsHandler(ball);
		ball.registerUpdateHandler(physicsHandler);
		final Body body = PhysicsFactory.createBoxBody(this.mPhysicsWorld, ball, BodyType.DynamicBody, FIXTURE_DEF);
		ball.setUserData(body);
		ball.animate(200);
		body.setUserData(makeUserData(100, body, ball));
		body.setLinearVelocity(random.nextInt(3) + 1, random.nextInt(3) - 3);
		this.mScene.attachChild(ball);
		this.mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(ball, body, true, true));

	}

	@Override
	public void onLoadComplete() {
		this.mEngine.enableVibrator(this);
	}

	@Override
	public boolean onSceneTouchEvent(final Scene pScene, final TouchEvent pSceneTouchEvent) {

		/////  pinch + scroll
		if (this.mPinchZoomDetector != null) {
			this.mPinchZoomDetector.onTouchEvent(pSceneTouchEvent);

			if (this.mPinchZoomDetector.isZooming()) {
				this.mScrollDetector.setEnabled(false);
			} else {
				if (pSceneTouchEvent.isActionDown()) {
					this.mScrollDetector.setEnabled(true);
				}
				this.mScrollDetector.onTouchEvent(pSceneTouchEvent);
			}
		} else {
			this.mScrollDetector.onTouchEvent(pSceneTouchEvent);
		}

		if (this.mPhysicsWorld != null) {
			switch (pSceneTouchEvent.getAction()) {
			case TouchEvent.ACTION_DOWN:
				this.addFace(pSceneTouchEvent.getX(), pSceneTouchEvent.getY());
				return true;
			case TouchEvent.ACTION_MOVE:
				if (this.mMouseJointActive != null) {
					final Vector2 vec = Vector2Pool.obtain(pSceneTouchEvent.getX() / PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT, pSceneTouchEvent.getY()
							/ PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT);
					this.mMouseJointActive.setTarget(vec);
					Vector2Pool.recycle(vec);
				}
				return true;
			case TouchEvent.ACTION_UP:
				if (this.mMouseJointActive != null) {
					this.mPhysicsWorld.destroyJoint(this.mMouseJointActive);
					this.mMouseJointActive = null;
				}
				return true;
			}
			return false;
		}
		return false;
	}

	@Override
	public boolean onAreaTouched(final TouchEvent pSceneTouchEvent, final ITouchArea pTouchArea, final float pTouchAreaLocalX, final float pTouchAreaLocalY) {
		if (pSceneTouchEvent.isActionDown()) {
			final IShape face = (IShape) pTouchArea;
			/*
			 * If we have a active MouseJoint, we are just moving it around
			 * instead of creating a second one.
			 */
			if (this.mMouseJointActive == null) {
				this.mEngine.vibrate(100);
				this.mMouseJointActive = this.createMouseJoint(face, pTouchAreaLocalX, pTouchAreaLocalY);
			}
			return true;
		}
		return false;
	}

	@Override
	public void onAccelerometerChanged(final AccelerometerData pAccelerometerData) {
		final Vector2 gravity = Vector2Pool.obtain(pAccelerometerData.getX(), pAccelerometerData.getY());
		this.mPhysicsWorld.setGravity(gravity);
		Vector2Pool.recycle(gravity);
	}

	@Override
	public void onResumeGame() {
		super.onResumeGame();

		// this.enableAccelerometerSensor(this);
	}

	@Override
	public void onPauseGame() {
		super.onPauseGame();

		this.disableAccelerometerSensor();
	}

	// ===========================================================
	// Methods
	// ===========================================================

	public MouseJoint createMouseJoint(final IShape pFace, final float pTouchAreaLocalX, final float pTouchAreaLocalY) {
		final Body body = (Body) pFace.getUserData();
		final MouseJointDef mouseJointDef = new MouseJointDef();

		final Vector2 localPoint = Vector2Pool.obtain((pTouchAreaLocalX - pFace.getWidth() * 0.5f) / PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT,
				(pTouchAreaLocalY - pFace.getHeight() * 0.5f) / PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT);
		this.mGroundBody.setTransform(localPoint, 0);

		mouseJointDef.bodyA = this.mGroundBody;
		mouseJointDef.bodyB = body;
		mouseJointDef.dampingRatio = 0.95f;
		mouseJointDef.frequencyHz = 30;
		mouseJointDef.maxForce = (200.0f * body.getMass());
		mouseJointDef.collideConnected = true;

		mouseJointDef.target.set(body.getWorldPoint(localPoint));
		Vector2Pool.recycle(localPoint);

		return (MouseJoint) this.mPhysicsWorld.createJoint(mouseJointDef);
	}

	private void addFace(final float pX, final float pY) {
		// this.mFaceCount++;
		// Debug.d("Faces: " + this.mFaceCount);
		// TODO

		final AnimatedSprite face;
		final Body body;

		// if (this.mFaceCount % 2 == 0) {
		face = new AnimatedSprite(pX, pY, this.mBoxFaceTextureRegion);
		body = PhysicsFactory.createBoxBody(this.mPhysicsWorld, face, BodyType.DynamicBody, FIXTURE_DEF);
		// } else {
		// face = new AnimatedSprite(pX, pY, this.mCircleFaceTextureRegion);
		// body = PhysicsFactory.createCircleBody(this.mPhysicsWorld, face,
		// BodyType.DynamicBody, FIXTURE_DEF);
		// }

		// TODO

		face.setUserData(body);
		face.animate(200);
		body.setUserData(makeUserData(100, body, face));
		this.mScene.registerTouchArea(face);
		this.mScene.attachChild(face);

		this.mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(face, body, true, true));
	}

	// ===========================================================
	// Inner and Anonymous Classes
	// ===========================================================

	private static class Ball extends AnimatedSprite {
		private final PhysicsHandler mPhysicsHandler;

		public Ball(final float pX, final float pY, final TiledTextureRegion pTextureRegion) {
			super(pX, pY, pTextureRegion);

			this.mPhysicsHandler = new PhysicsHandler(this);
			this.registerUpdateHandler(this.mPhysicsHandler);
		}

		// @Override
		// protected void onManagedUpdate(final float pSecondsElapsed) {
		// if (this.mX < 0) {
		// this.mPhysicsHandler.setVelocityX(DEMO_VELOCITY);
		// } else if (this.mX + this.getWidth() > CAMERA_WIDTH) {
		// this.mPhysicsHandler.setVelocityX(-DEMO_VELOCITY);
		// }
		//
		// if (this.mY < 0) {
		// this.mPhysicsHandler.setVelocityY(DEMO_VELOCITY);
		// } else if (this.mY + this.getHeight() > CAMERA_HEIGHT) {
		// this.mPhysicsHandler.setVelocityY(-DEMO_VELOCITY);
		// }
		//
		// super.onManagedUpdate(pSecondsElapsed);
		// }
	}

	@Override
	public void beginContact(Contact contact) {

		// if (badyA.getType() == BodyType.DynamicBody) {
		// // badyA.setActive(false);
		// }

		Body badyA = contact.getFixtureA().getBody();
		Body badyB = contact.getFixtureB().getBody();

		JSONObject obj1UserData = null;
		JSONObject obj2UserData = null;
		if (badyB.getUserData() != null && badyA.getUserData() != null) {
			obj1UserData = (JSONObject) badyA.getUserData();
			obj2UserData = (JSONObject) badyB.getUserData();
			try {
				int type1 = obj1UserData.getInt("type");
				int type2 = obj2UserData.getInt("type");
				if (type1 == 100 && type2 == 100) {
					obj1UserData.put("deleteStatus", true);
					obj2UserData.put("deleteStatus", true);
				} else if (type1 == 100 && type2 == 101) {
					obj1UserData.put("deleteStatus", true);
				} else if (type1 == 101 && type2 == 100) {
					obj2UserData.put("deleteStatus", true);
				}
			} catch (JSONException e) {
				e.printStackTrace();
			}
			Log.e("11", obj1UserData.toString());
		}

	}

	@Override
	public void endContact(Contact contact) {

	}

	@Override
	public void preSolve(Contact contact, Manifold oldManifold) {

	}

	@Override
	public void postSolve(Contact contact, ContactImpulse impulse) {

	}

	@Override
	public void onUpdate(float pSecondsElapsed) {
		// remove any sprites flagged for deletion
		try {
			removeObjectsSetForDestruction();
		} catch (Exception e) {
			Log.d("", "Exception removing objects from update:" + e);
		} catch (Error e) {
			Log.d("", "Error removing objects from update:" + e);
		}
	}

	@Override
	public void reset() {

	}

	private JSONObject makeUserData(int type, Body body, Object sprite) {
		JSONObject myObject = new JSONObject();

		try {
			myObject.put("type", type);
			myObject.put("sprite", sprite);
			myObject.put("body", body);
			myObject.put("deleteStatus", false);
		} catch (JSONException e) {
			Log.d("", "Exception creating user data:" + e);
		}
		return myObject;
	}

	private void removeObjectsSetForDestruction() {
		if (this.mPhysicsWorld != null) {

			Iterator<Body> allMyBodies = this.mPhysicsWorld.getBodies();// gets
																		// all
																		// the
																		// bodies
																		// in my
																		// physics
																		// world
			boolean isDelete = false;
			JSONObject currentBodyData;
			while (allMyBodies.hasNext()) {
				try {
					// this code is in a try/catch bracket because some of my
					// bodies don't have the extra data attached
					currentBodyData = (JSONObject) allMyBodies.next().getUserData();// gets
																					// the
																					// next
																					// JSONOBject
																					// from
																					// the
																					// body
																					// Log.e("",
																					// ""
																					// +
																					// currentBodyData);
					if (currentBodyData != null) {
						isDelete = (Boolean) currentBodyData.get("deleteStatus");
						// Log.e("isDelete", " " + isDelete);
						if (isDelete) {
							destroyObstruction((Body) currentBodyData.get("body"));
						}
					}
				} catch (JSONException e) {
					Log.d("", "Error getting world bodies data:" + e);
				}
			}
		}
	}

	private void destroyObstruction(Body obstructionBody) throws JSONException {
		obstructionBody.setActive(false);

		JSONObject secondBodyData = null;
		if (obstructionBody.getUserData() != null) {
			secondBodyData = (JSONObject) obstructionBody.getUserData();

			// explodeObstruction(((IEntity)
			// secondBodyData.get("sprite")).getX(),((IEntity)
			// secondBodyData.get("sprite")).getY());
			if (secondBodyData.get("sprite") instanceof AnimatedSprite) {
				removeObject((Shape) secondBodyData.get("sprite"));
			} else {
				// removeObject((Sprite) secondBodyData.get("sprite"));
			}
		}

	}

	// private void removeObject(final AnimatedSprite myAnimSprite) {
	// final PhysicsConnector myPhysicsConnector =
	// this.mPhysicsWorld.getPhysicsConnectorManager().findPhysicsConnectorByShape(myAnimSprite);
	//
	// this.mPhysicsWorld.unregisterPhysicsConnector(myPhysicsConnector);
	// this.mPhysicsWorld.destroyBody(myPhysicsConnector.getBody());
	//
	// this.mScene.unregisterTouchArea(myAnimSprite);
	// this.mScene.detachChild(myAnimSprite);
	//
	// System.gc();
	// }

	private void removeObject(final Shape mySprite) {
		final PhysicsConnector myPhysicsConnector = this.mPhysicsWorld.getPhysicsConnectorManager().findPhysicsConnectorByShape(mySprite);

		this.mPhysicsWorld.unregisterPhysicsConnector(myPhysicsConnector);
		this.mPhysicsWorld.destroyBody(myPhysicsConnector.getBody());

		this.mScene.unregisterTouchArea(mySprite);
		this.mScene.detachChild(mySprite);

		System.gc();
	}

	// private Handler handler = new Handler() {
	// @Override
	// public void dispatchMessage(Message msg) {
	// super.dispatchMessage(msg);
	// addBall();
	// }
	// };

	@Override
	public void onScroll(final ScrollDetector pScollDetector, final TouchEvent pTouchEvent, final float pDistanceX, final float pDistanceY) {
		final float zoomFactor = this.mZoomCamera.getZoomFactor();
		this.mZoomCamera.offsetCenter(-pDistanceX / zoomFactor, -pDistanceY / zoomFactor);
	}

	@Override
	public void onPinchZoomStarted(final PinchZoomDetector pPinchZoomDetector, final TouchEvent pTouchEvent) {
		this.mPinchZoomStartedCameraZoomFactor = this.mZoomCamera.getZoomFactor();
	}

	@Override
	public void onPinchZoom(final PinchZoomDetector pPinchZoomDetector, final TouchEvent pTouchEvent, final float pZoomFactor) {
		this.mZoomCamera.setZoomFactor(this.mPinchZoomStartedCameraZoomFactor * pZoomFactor);
	}

	@Override
	public void onPinchZoomFinished(final PinchZoomDetector pPinchZoomDetector, final TouchEvent pTouchEvent, final float pZoomFactor) {
		this.mZoomCamera.setZoomFactor(this.mPinchZoomStartedCameraZoomFactor * pZoomFactor);
	}

	// @Override
	// public boolean onSceneTouchEvent(final Scene pScene, final TouchEvent
	// pSceneTouchEvent) {
	// if(this.mPinchZoomDetector != null) {
	// this.mPinchZoomDetector.onTouchEvent(pSceneTouchEvent);
	//
	// if(this.mPinchZoomDetector.isZooming()) {
	// this.mScrollDetector.setEnabled(false);
	// } else {
	// if(pSceneTouchEvent.isActionDown()) {
	// this.mScrollDetector.setEnabled(true);
	// }
	// this.mScrollDetector.onTouchEvent(pSceneTouchEvent);
	// }
	// } else {
	// this.mScrollDetector.onTouchEvent(pSceneTouchEvent);
	// }
	//
	// return true;
	// }

}
